#include "grflib_stdafx.h"
#include "../include/SceneObj.h"
#include "../include/D3D10Engine.h"
#include <algorithm>

namespace grflib { namespace engine { namespace core {

typedef std::vector< boost::shared_ptr<IRenderable> > RenderableVec;

CSceneObj::CSceneObj() :
    m_isVisible(true),
    m_isCullable(true),
    m_priority(0),
    m_position(0.0f, 0.0f, 0.0f),
    m_worldCoordUpToDate(false),
    m_scale(1.0f, 1.0f, 1.0f)
{
    D3DXMatrixIdentity(&m_rotationMat);
    D3DXMatrixIdentity(&m_worldMat);
}

CSceneObj::CSceneObj(UINT priority)
    : m_priority(priority),
    m_isVisible(true),
    m_isCullable(true),
    m_position(0.0f, 0.0f, 0.0f),
    m_worldCoordUpToDate(false),
    m_scale(1.0f, 1.0f, 1.0f)
{
    D3DXMatrixIdentity(&m_rotationMat);
    D3DXMatrixIdentity(&m_worldMat);
}

CSceneObj::~CSceneObj()
{
    m_pParent.reset();
    m_renderables.clear();
}

void CSceneObj::UpdateBBox(const D3DXMATRIX &mat)
{
    if (m_renderables.size() > 0)
        m_worldBBox = m_renderables[0]->GetBoundingBox();

    if (m_renderables.size() > 1)
    {
        for (RenderableVec::size_type i = 1; i < m_renderables.size(); i++)
        {
            m_worldBBox.Merge(m_renderables[i]->GetBoundingBox());
        }
    }

    //prepare 8 corner values
    float corners[8][3] = {0};
    for ( size_t i = 0; i < 2; ++i )
    {
        for ( size_t j = 0; j < 2; ++j )
        {
            for ( size_t k = 0; k < 2; ++k )
            {
                size_t index = i*4 + j*2 + k;
                if ( i == 0 )
                    corners[index][0] = m_worldBBox.m_min[0];
                else
                    corners[index][0] = m_worldBBox.m_max[0];
                if ( j == 0 )
                    corners[index][1] = m_worldBBox.m_min[1];
                else
                    corners[index][1] = m_worldBBox.m_max[1];
                if ( k == 0 )
                    corners[index][2] = m_worldBBox.m_min[2];
                else
                    corners[index][2] = m_worldBBox.m_max[2];
            }
        }
    }

    //calc new corner values, update the new_min and new_max
    D3DXVECTOR4 result;
    D3DXVECTOR3 corner;
    for ( size_t i = 0; i < 8; ++i )
    {
        //vector and matrix multiplication
        corner.x = corners[i][0];
        corner.y = corners[i][1];
        corner.z = corners[i][2];
        D3DXVec3Transform(&result, &corner, &mat);
        corners[i][0] = result.x;
        corners[i][1] = result.y;
        corners[i][2] = result.z;
    }

    m_worldBBox.Clear();
    for ( size_t i = 0; i < 8; ++i )
    {
        for ( size_t j = 0; j < 3; ++j )
        {
            m_worldBBox.AddMinMax(j, corners[i][j]);
        }
    }

    for (size_t k = 0; k < 3; ++k)
    {
        m_worldBBox.m_center[k] = (m_worldBBox.m_max[k] + m_worldBBox.m_min[k]) / 2.0f;
        m_worldBBox.m_halfsize[k] = (m_worldBBox.m_max[k] - m_worldBBox.m_min[k]) / 2.0f;
    }
}

//the basis CSceneObj only calls FrameMove of renderables,
//other work should be done in inherited class
void CSceneObj::FrameMove(double fTime, float fElapsedTime)
{
    for (RenderableVec::iterator iter = m_renderables.begin();
        iter != m_renderables.end();
        ++iter)
    {
        (*iter)->FrameMove(fTime, fElapsedTime);
    }
}

bool CSceneObj::Attach(boost::shared_ptr<IRenderable> &pObj)
{
    RenderableVec::iterator iter =
        std::find(m_renderables.begin(), m_renderables.end(), pObj);
    if (iter == m_renderables.end())
        m_renderables.push_back(pObj);
    return true;
}

void CSceneObj::Detach(boost::shared_ptr<IRenderable> &pObj)
{
    RenderableVec::iterator iter =
        std::find(m_renderables.begin(), m_renderables.end(), pObj);
    if (iter != m_renderables.end())
        m_renderables.erase(iter);
}

void CSceneObj::DetachAll()
{
    m_renderables.clear();
}

const BBox<float> &CSceneObj::GetWorldBBox()
{
    return m_worldBBox;
}

const D3DXMATRIX &CSceneObj::GetWorldMat()
{
    return m_worldMat;
}

grflib::LA::CVec3<float> &CSceneObj::UpdateWorldPos()
{
    CSceneObj *pObj = this;
    m_worldPos.SetValue(0.0f);
    while (pObj)
    {
        m_worldPos += pObj->m_position;
        pObj = pObj->m_pParent.get();
    }
    return m_worldPos;
}

const D3DXMATRIX &CSceneObj::UpdateWorldMat()
{
    UpdateWorldPos();

    float fScale[3] = {1.0f, 1.0f, 1.0f};

    D3DXMatrixIdentity(&m_tmpMat);
    D3DXMatrixIdentity(&m_worldMat);
    CSceneObj *pObj = this;
    while (pObj != 0)
    {
        fScale[0] *= pObj->m_scale._x; fScale[1] *= pObj->m_scale._y; fScale[2] *= pObj->m_scale._z;
        m_tmpMat *= pObj->m_rotationMat;
        pObj = (pObj->m_pParent).get();
    }

    D3DXMatrixScaling(&m_tmpMat2, fScale[0], fScale[1], fScale[2]);
    m_worldMat *= m_tmpMat2 /*scaling*/ * m_tmpMat /*rotation*/;
    D3DXMatrixTranslation(&m_tmpMat2, m_worldPos._x, m_worldPos._y, m_worldPos._z);
    m_worldMat *= m_tmpMat2 /*translation*/;
    return m_worldMat;
}

bool CSceneObj::UpdateWorldCoord()
{
    CSceneObj *pObj = this;
    while (pObj)
    {
        if (!pObj->m_worldCoordUpToDate)
        {
            UpdateBBox( UpdateWorldMat() );
            return true;
        }
        pObj = pObj->m_pParent.get();
    }
    return false;
}

bool CSceneObj::SetParent(char *szParent)
{
    boost::shared_ptr<CSceneObj> &pObj = _SceneMan.FindObj(szParent);
    return SetParent(pObj);
}

bool CSceneObj::SetParent(boost::shared_ptr<CSceneObj> &pObj)
{
    if (!pObj)
        return false;

    //check for circular reference
    CSceneObj *pTmp = pObj.get();
    while (pTmp != 0)
    {
        if (pTmp == this)
            return false;
        pTmp = pTmp->m_pParent.get();
    }

    m_pParent = pObj;
    return true;
}

void CSceneObj::ResetUpToDateFlag()
{
    m_worldCoordUpToDate = true;
}


void CSceneObj::MoveTo(float x, float y, float z)
{
    m_position._x = x;
    m_position._y = y;
    m_position._z = z;
    m_worldCoordUpToDate = false;
}

void CSceneObj::Rotate(const D3DXMATRIX &rotMat, bool reset)
{
    if (reset)
        m_rotationMat = rotMat;
    else
        D3DXMatrixMultiply(&m_rotationMat, &m_rotationMat, &rotMat);
    m_worldCoordUpToDate = false;
}

void CSceneObj::Scale(float x, float y, float z, bool reset)
{
    if (reset)
    {
        m_scale._x = x;
        m_scale._y = y;
        m_scale._z = z;
    }
    else
    {
        m_scale._x *= x;
        m_scale._y *= y;
        m_scale._z *= z;
    }
    m_worldCoordUpToDate = false;
}

}}} //namespaces